using System.Net;
using SuperSocket.Channel;

namespace SuperSocket.Client.Proxy
{
    public abstract class ProxyConnectorBase<TReceivePackage> 
        : ConnectorBase where TReceivePackage : class
    {
        protected string Username;
        protected string Password;
        protected EndPoint ProxyEndPoint;

        public ProxyConnectorBase(EndPoint proxyEndPoint)
            : this(proxyEndPoint, null)
        {
        }

        public ProxyConnectorBase(EndPoint proxyEndPoint, IConnector nextConnector)
            : this(proxyEndPoint, null, null, nextConnector)
        {
        }

        public ProxyConnectorBase(EndPoint proxyEndPoint, string username, string password, IConnector nextConnector) : base(nextConnector)
        {
            Username = username;
            Password = password;
            ProxyEndPoint = proxyEndPoint;
        }

        protected abstract IChannel<TReceivePackage> CreateChannel(ConnectState state, ChannelOptions channelOptions);

        protected abstract ValueTask<bool> ConnectProxyAsync(EndPoint remoteEndPoint, IChannel<TReceivePackage> channel, IAsyncEnumerator<TReceivePackage> packStream, CancellationToken cancellationToken);

        protected override async ValueTask<ConnectState> ConnectAsync(EndPoint remoteEndPoint, ConnectState state, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            
            var proxyEndPoint = ProxyEndPoint;
            var socketConnector = new SocketConnector() as IConnector;

            ConnectState result;

            try
            {
                result = await socketConnector.ConnectAsync(proxyEndPoint, null, cancellationToken);

                if (!result.Result)
                    return result;
            }
            catch (Exception e)
            {
                return new ConnectState
                {
                    Result = false,
                    Exception = e
                };
            }

            var channel = CreateChannel(result, new ChannelOptions { ReadAsDemand = true });

            channel.Start();

            var packStream = channel.GetPackageStream();

            result.Result = false;

            try
            {
                result.Result = await ConnectProxyAsync(remoteEndPoint, channel, packStream, cancellationToken);
            }
            catch (Exception e)
            {
                return new ConnectState
                {
                    Result = false,
                    Exception = e
                };
            }
            finally
            {
                if (!result.Result)
                    await channel.CloseAsync(CloseReason.ProtocolError);
            }

            if (result.Result)
                await channel.DetachAsync();

            return result;
        }
    }
}